package com.mf.storage.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.mf.storage.property.StorageProperties;
import com.mf.storage.service.ObjectStorageService;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.exception.MultiObjectDeleteException.DeleteError;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.*;
import com.qcloud.cos.model.DeleteObjectsRequest.KeyVersion;
import com.qcloud.cos.model.DeleteObjectsResult.DeletedObject;
import com.qcloud.cos.region.Region;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 腾讯COS对象存储实现类
 * User: zhaoming
 * Date: 2019-02-13
 * To change this template use File | Settings | File Templates.
 **/
public class CosObjectStorageServiceImpl implements ObjectStorageService {

    private Logger logger = LoggerFactory.getLogger(getClass());
    private StorageProperties storageProperties;

    public CosObjectStorageServiceImpl(StorageProperties storageProperties){
        this.storageProperties = storageProperties;
    }

    public COSClient getCosClient(){
        // 1 初始化用户身份信息(secretId, secretKey)
        COSCredentials cred = new BasicCOSCredentials(storageProperties.getAccessKey(), storageProperties.getSecretKey());
        // 2 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224
        ClientConfig clientConfig = new ClientConfig(new Region(storageProperties.getRegion()));
        // 3 生成cos客户端
        return new COSClient(cred, clientConfig);
    }

    /**
     * 上传文件对象
     * @param bucketName 存储名称
     * @param objectName 对象名称
     * @param file 文件
     */
    public void putObject(String bucketName, String objectName, File file){
        logger.info("方法：上传文件对象");
        logger.info("参数：bucketName: {}, objectName: {}", bucketName, objectName);
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            PutObjectResult putObjectResult = cosClient.putObject(bucketName, objectName, file);
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms，返回参数：{}", endTime - startTime, JSONObject.toJSONString(putObjectResult));
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 上传输入流对象
     * @param bucketName 存储名称
     * @param objectName 对象名称
     * @param in 输入流
     */
    public void putObject(String bucketName, String objectName, InputStream in){
        logger.info("方法：上传输入流对象");
        logger.info("参数：bucketName: {}, objectName: {}", bucketName, objectName);
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            ObjectMetadata objectMetadata = new ObjectMetadata();
            // 从输入流上传必须制定content length, 否则http客户端可能会缓存所有数据，存在内存OOM的情况
            objectMetadata.setContentLength(in.available());
            // 默认下载时根据cos路径key的后缀返回响应的contenttype, 上传时设置contenttype会覆盖默认值
            // objectMetadata.setContentType("application/octet-stream");
            PutObjectResult putObjectResult = cosClient.putObject(bucketName, objectName, in, objectMetadata);
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms，返回参数：{}", endTime - startTime, JSONObject.toJSONString(putObjectResult));
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException | IOException e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 下载文件到输入流
     * @param bucketName 存储名称
     * @param objectName 对象名称
     * @return 字节数组
     */
    public byte[] getObject(String bucketName, String objectName){
        logger.info("方法：下载文件到输入流");
        logger.info("参数：bucketName: {}, objectName: {}", bucketName, objectName);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectName);
            COSObject cosObject = cosClient.getObject(getObjectRequest);
            COSObjectInputStream cosObjectInput = cosObject.getObjectContent();
            byte[] buffer = new byte[1024];
            int len;
            while((len = cosObjectInput.read(buffer))!=-1){
                out.write(buffer, 0, len);
            }
            cosObject.close();
            out.close();
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms，返回参数：{}", endTime - startTime);
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException | IOException e){
            throw new RuntimeException(e.getMessage(), e);
        }
        return out.toByteArray();
    }

    /**
     * 下载文件到本地
     * @param bucketName 存储名称
     * @param objectName 对象名称
     * @param file 文件
     */
    public void getObject(String bucketName, String objectName, File file){
        logger.info("方法：下载文件到本地");
        logger.info("参数：bucketName: {}, objectName: {}", bucketName, objectName);
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectName);
            cosClient.getObject(getObjectRequest, file);
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms，返回参数：{}", endTime - startTime);
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 删除单个对象
     * @param bucketName 存储名称
     * @param objectName 对象名称
     */
    public void deleteObject(String bucketName, String objectName){
        logger.info("方法：删除单个文件");
        logger.info("参数：bucketName: {}, objectName: {}", bucketName, objectName);
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            cosClient.deleteObject(bucketName, objectName);
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms", endTime - startTime);
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 删除多个对象
     * @param bucketName 存储名称
     * @param objectNames 对象名称列表
     * @return 执行结果
     */
    public Map<String, List<String>> deleteObjects(String bucketName, List<String> objectNames){
        logger.info("方法：删除多个文件");
        logger.info("参数：bucketName: {}, objectName: {}", bucketName, JSONObject.toJSONString(objectNames));
        Map<String, List<String>> map = new HashMap<>();
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
            deleteObjectsRequest.setKeys(objectNames.stream().map(KeyVersion::new).collect(Collectors.toList()));
            DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);
            List<DeletedObject> deletedObjects = deleteObjectsResult.getDeletedObjects();
            map.put("success", deletedObjects.stream().map(DeletedObject::getKey).collect(Collectors.toList()));
            map.put("error", new ArrayList<>());
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms", endTime - startTime);
        } catch (MultiObjectDeleteException mde) {
            // 如果部分产出成功部分失败, 返回MultiObjectDeleteException
            List<DeletedObject> deletedObjects = mde.getDeletedObjects();
            List<DeleteError> deleteErrors = mde.getErrors();
            map.put("success", deletedObjects.stream().map(DeletedObject::getKey).collect(Collectors.toList()));
            map.put("error", deleteErrors.stream().map(DeleteError::getKey).collect(Collectors.toList()));
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException e){
            throw new RuntimeException(e.getMessage(), e);
        }
        return map;
    }

    /**
     * 获取对象URL
     * @param bucketName 存储名称
     * @param objectName 对象名称
     * @return 对象访问URL
     */
    public String getObjectUrl(String bucketName, String objectName){
        return MessageFormat.format("https://{0}.cos.{1}.myqcloud.com/{2}", bucketName, storageProperties.getRegion(), objectName);
    }

    /**
     * 获取对象列表
     * @param bucketName 存储名称
     * @param prefix 前缀
     * @param marker 标记
     * @param delimiter 对象名称分组
     * @param maxKeys 最大个数（默认值为1000，最大值为1000）
     * @return 对象列表
     */
    public List<String> listObjects(String bucketName, String prefix, String marker, String delimiter, Integer maxKeys){
        logger.info("方法：获取对象列表");
        logger.info("参数：bucketName: {}, prefix: {}, marker: {}, delimiter: {}, maxKeys: {}", bucketName, prefix, marker, delimiter, maxKeys);
        List<String> keys;
        try{
            long startTime = System.currentTimeMillis();
            COSClient cosClient = getCosClient();
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
            listObjectsRequest.setBucketName(bucketName);
            // 设置 list 的 prefix, 表示 list 出来的文件 key 都是以这个 prefix 开始
            listObjectsRequest.setPrefix(prefix);
            // 设置 delimiter 为/, 即获取的是直接成员，不包含目录下的递归子成员
            listObjectsRequest.setDelimiter(delimiter);
            // 设置 marker, (marker 由上一次 list 获取到, 或者第一次 list marker 为空)
            listObjectsRequest.setMarker(marker);
            // 设置最多 list 100个成员,（如果不设置, 默认为1000个，最大允许一次 list 1000个 key）
            listObjectsRequest.setMaxKeys(maxKeys);
            ObjectListing objectListing = cosClient.listObjects(listObjectsRequest);
            List<COSObjectSummary> objectSummaries = objectListing.getObjectSummaries();
            keys = objectSummaries.stream().map(COSObjectSummary::getKey).collect(Collectors.toList());
            cosClient.shutdown();
            long endTime = System.currentTimeMillis();
            logger.info("执行成功，用时：{}ms", endTime - startTime);
        } catch (CosServiceException e){
            throw new RuntimeException(e.getErrorMessage(), e);
        } catch (CosClientException e){
            throw new RuntimeException(e.getMessage(), e);
        }
        return keys;
    }

    public static void main(String[] args) throws IOException {
        StorageProperties storageProperties = new StorageProperties();
        storageProperties.setType("COS");
        storageProperties.setRegion("ap-beijing");
        storageProperties.setAccessKey("AKIDJWkvvjrAg4Thv2HmO0dbbQJ6c3JOl9n2");
        storageProperties.setSecretKey("HPMAnIcsCUffWCgzGMH0hHgjrpcHnCRZ");
        String bucketName = "photos-1256650050";
        String key = "20181016171401.jpg";
        File file = new File("c://20181016171401.jpg");
        Date expiration = new Date(new Date().getTime() + 6 * 3600 * 1000);
        CosObjectStorageServiceImpl cosObjectService = new CosObjectStorageServiceImpl(storageProperties);
        System.out.println(cosObjectService.getObjectUrl(bucketName, key));
    }
}
